// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MarketBookBuilder.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Usings

    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// The market book builder is a transparent layer to transform quote based events (no depth information ) 
    /// into market book based events (with depth). For this purpose it rebuilds internally
    /// a market book and issues book events if needed. A set of rules is important for the builder to work
    /// - Each quote must have a unique QuoteKey per side (that is the same QuoteKey is possible, one for sellers one for buyers)
    /// - A quote can be updated, that is, the size can be modified (but not the price!)
    /// - Each quote must be eventually closed, else it will persist in the book forever 
    /// </summary>
    public class MarketBookBuilder : IMarketBookBuilder
    {
        #region Constants and Fields

        /// <summary>
        /// The buyers.
        /// </summary>
        private readonly MarketQuotesListDictionary.Buyers buyers;

        /// <summary>
        /// The sellers.
        /// </summary>
        private readonly MarketQuotesListDictionary.Sellers sellers;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MarketBookBuilder"/> class.
        /// </summary>
        /// <param name="depth">
        /// The depth.
        /// </param>
        public MarketBookBuilder(short depth)
        {
            this.buyers = new MarketQuotesListDictionary.Buyers();
            this.sellers = new MarketQuotesListDictionary.Sellers();
            this.Book = new MarketBook(depth);
        }

        #endregion

        #region Properties

        /// <summary>
        /// The sellers.
        /// </summary>
        public IMarketBook Book { get; private set; }

        #endregion

        #region Implemented Interfaces

        #region IMarketBookBuilder

        /// <summary>
        /// The handle updates.
        /// </summary>
        /// <param name="marketQuoteUpdates">
        /// The market quote updates.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="Exception">
        /// </exception>
        public FeedEventArgs.MarketBookUpdate HandleUpdate(FeedEventArgs.MarketQuoteUpdate marketQuoteUpdates)
        {
            // This list will hold all the marketQuoteUpdates produced by the arrival of this new quote
            var marketBookCells = new List<MarketBookCell>();

            foreach (var marketQuote in marketQuoteUpdates.Quotes)
            {
                // string bookBefore = DumpBook();

                // Let's process this quote
                if (marketQuote.Side == ESide.Bid)
                {
                    marketBookCells.AddRange(this.BuildMarketBookCellsList(ESide.Bid, this.buyers.HandleQuote(marketQuote, this.Book.Depth)));
                }
                else if (marketQuote.Side == ESide.Ask)
                {
                    marketBookCells.AddRange(this.BuildMarketBookCellsList(ESide.Ask, this.sellers.HandleQuote(marketQuote, this.Book.Depth)));
                }
                else
                {
                    throw new Exception("MarketBookBuilder: Quote has no side info !");
                }

                // string bookAfter = DumpBook();
                // CheckBook(quote,bookBefore,bookAfter);

                // DateTime t2 = Timings.Now;
                // TimeSpan dt = t2 - t1;
                // Stdout.WriteLine("(dt=" + dt.TotalMilliseconds + ")");

                // Stdout.WriteLine("Book after");
                // Stdout.WriteLine(DumpBook());

                // Stdout.WriteLine("-----------------------------------------------------------------------------------");

                // Stdout.WriteLine(" Book Fields sent " + PrettyPrinter.Timings.Format(Timings.Now));
            }

            return new FeedEventArgs.MarketBookUpdate(marketQuoteUpdates.ObjectID, marketBookCells.ToArray(), marketQuoteUpdates.IsSnapshot);
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The build market book updates list.
        /// </summary>
        /// <param name="side">
        /// The side.
        /// </param>
        /// <param name="modifications">
        /// The modifications.
        /// </param>
        /// <returns>
        /// </returns>
        private IList<MarketBookCell> BuildMarketBookCellsList(ESide side, MarketQuotesListDictionary.Modifications modifications)
        {
            // Fields produced by given modifications
            IList<MarketBookCell> updates = new List<MarketBookCell>();

            // No modifications at all
            if (modifications == null)
            {
                return updates;
            }

            // Stdout.WriteLine();
            // The indexes that have changed in this side are
            foreach (var depth in modifications.ModifiedDepthIndexes)
            {
                if (depth < 0)
                {
                    continue;
                }

                var buyerPrices = this.buyers.Keys.ToArray();
                var sellerPrices = this.sellers.Keys.ToArray();

                // depths below NewLength holds uptodate price/size data
                if (depth < modifications.NewLength)
                {
                    MarketQuoteList list;
                    switch (side)
                    {
                        case ESide.Bid:
                            list = this.buyers[buyerPrices[depth]];
                            updates.Add(MarketBookCell.Bid(depth, Timings.Now, list.Price, list.TotalSize, list.Contributions));
                            break;

                        case ESide.Ask:
                            list = this.sellers[sellerPrices[depth]];
                            updates.Add(MarketBookCell.Ask(depth, Timings.Now, list.Price, list.TotalSize, list.Contributions));
                            break;
                        default:
                            continue;
                    }

                    // Stdout.Write(" " + depth);                    
                }
                else
                {
                    // over NewLength price/size got removed
                    if (depth < modifications.PreviousLength)
                    {
                        throw new Exception("TODO (Refactoring FeedChain)");

                        //// Empty update (that is "this depth is now empty for this side")
                        ////var b = new MarketBookCell();
                        ////b.Side = side;
                        ////b.Depth = depth;
                        ////updates.Add(new MarketBookCell());
                    }
                }
            }

            // Fields are now ready
            return updates;
        }

        /// <summary>
        /// The check book.
        /// </summary>
        /// <param name="quote">
        /// The quote.
        /// </param>
        /// <param name="bookBefore">
        /// The bookBefore.
        /// </param>
        /// <param name="bookAfter">
        /// The bookAfter.
        /// </param>
        private void CheckBook(MarketQuote quote, string bookBefore, string bookAfter)
        {
            // Self Crossing : If Best bid equals Best Ask
            var selfCrossing = this.buyers.Keys.Count > 0 && this.sellers.Keys.Count > 0 && this.buyers.Keys.First() == this.sellers.Keys.First();

            // Flawed book : If concatenation of "buyers prices" (reversed)  and "sellers prices" leads to an unsorted list
            var flawed = false;
            IList<double> allPrices = new List<double>();
            allPrices.Concat(this.buyers.Keys.Reverse());
            allPrices.Concat(this.sellers.Keys);
            var allPricesSorted = new SortedArray<double>(new MarketQuotesListDictionary.Sellers.SellersComparer());
            allPricesSorted.AddRange(this.buyers.Keys.Reverse());
            allPricesSorted.AddRange(this.sellers.Keys);

            for (var i = 0; i < allPricesSorted.Length; i++)
            {
                if (allPricesSorted[i] != allPrices[i])
                {
                    flawed = true;
                    break;
                }
            }

            if (selfCrossing)
            {
                Stdout.WriteLine("** Self Crossing **");
                Stdout.WriteLine("BEFORE");
                Stdout.WriteLine(bookBefore);
                Stdout.WriteLine("AFTER");
                Stdout.WriteLine(bookAfter);
            }

            if (flawed)
            {
                Stdout.WriteLine("** Flawed after insertion of quote {0} **", quote);
                Stdout.WriteLine("BEFORE");
                Stdout.WriteLine(bookBefore);
                Stdout.WriteLine("AFTER");
                Stdout.WriteLine(bookAfter);
            }
        }

        /// <summary>
        /// The dump book.
        /// </summary>
        /// <returns>
        /// The dump book.
        /// </returns>
        private string DumpBook()
        {
            var s = string.Empty;

            var buyersPrices = this.buyers.Keys;
            var sellersPrices = this.sellers.Keys;

            var i = 0;
            while (true)
            {
                MarketQuoteList buyersList = null;
                MarketQuoteList sellersList = null;
                if (i < buyersPrices.Count)
                {
                    buyersList = this.buyers[buyersPrices.ElementAt(i)];
                }

                if (i < sellersPrices.Count)
                {
                    sellersList = this.sellers[sellersPrices.ElementAt(i)];
                }

                if (buyersList == null && sellersList == null)
                {
                    return s;
                }

                var bidSize = 0;
                var bidPrice = double.NaN;
                var askSize = 0;
                var askPrice = double.NaN;

                if (buyersList != null)
                {
                    bidPrice = buyersList.Price;
                    bidSize = buyersList.TotalSize;
                }

                if (sellersList != null)
                {
                    askPrice = sellersList.Price;
                    askSize = sellersList.TotalSize;
                }

                var inBook = i < this.Book.Depth ? "*" : " ";
                s += string.Format(
                    "{0} {1,15} {2,15} {3,15} {4,15}\n", 
                    inBook, 
                    ForgePrettyPrinter.Sizes.Format(bidSize), 
                    ForgePrettyPrinter.Prices.Format(bidPrice, Constants.Zero), 
                    ForgePrettyPrinter.Prices.Format(askPrice, Constants.Zero), 
                    ForgePrettyPrinter.Sizes.Format(askSize));
                i++;
            }
        }

        #endregion

        /// <summary>
        /// A sorted dictionary of MarketQuoteList. The sorting key is MarketQuoteList.Price.
        /// Sorting is ascending or descending, depending on the type of dictionary (one for sellers one for buyers)
        /// 
        /// Example of a buyers dictionay (the bid side) and a sellers dictionary (ask side). The first dictionary
        /// is in descending order (1,3113 &gt; 1,3112), the second one is in ascending order (1,3114 &lt; 1,3115)
        /// 
        /// Elements between brackets are contributors
        /// 
        ///  { 1M 2M 3M } 6M 1,3113 1,3114 3M { 1M 2M }
        ///        { 1M } 1M 1,3112 1,3115 1M { 1M }
        /// 
        /// This class insert MarketQuote to maintain an underlying IMarketBook
        /// </summary>
        internal class MarketQuotesListDictionary : SortedDictionary<double, MarketQuoteList>
        {
            #region Constants and Fields

            /// <summary>
            /// The quotePrice.
            /// </summary>
            private readonly IDictionary<string, double> quotePrice = new Dictionary<string, double>();

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="MarketQuotesListDictionary"/> class.
            /// </summary>
            /// <param name="comparer">
            /// The comparer.
            /// </param>
            public MarketQuotesListDictionary(IComparer<double> comparer)
                : base(comparer)
            {
            }

            #endregion

            #region Public Methods

            /// <summary>
            /// The handle marketQuoteUpdate.
            /// </summary>
            /// <param name="marketQuoteUpdate">
            /// The marketQuoteUpdate.
            /// </param>
            /// <param name="depth">
            /// The depth.
            /// </param>
            /// <returns>
            /// </returns>
            public Modifications HandleQuote(MarketQuote marketQuoteUpdate, int depth)
            {
                // keep track of prices array before marketQuoteUpdate is handled
                IList<double> pricesBefore = new List<double>(this.Keys);
                IList<double> pricesAfter = null;

                const int Decimals = 6;
                var quoteDotPrice = Math.Round(marketQuoteUpdate.Price, Decimals);

                var indexUpdated = -1;

                // If Quote is new -> Keep it if not closed
                if (!this.quotePrice.ContainsKey(marketQuoteUpdate.QuoteKey))
                {
                    // New marketQuoteUpdate, but closed, we don't close something we never been informed to open
                    if (marketQuoteUpdate.Closed)
                    {
                        return null;
                    }
                    else
                    {
                        this.quotePrice[marketQuoteUpdate.QuoteKey] = quoteDotPrice;
                    }
                }

                // If Quote price is not a key in the map, create a map entry quotePrice -> Quotelist { } 
                if (!marketQuoteUpdate.Closed && !this.ContainsKey(quoteDotPrice))
                {
                    var newQuoteList = new MarketQuoteList(quoteDotPrice);
                    this.Add(quoteDotPrice, newQuoteList);
                }

                // Because marketQuoteUpdate price is not set for a "close" marketQuoteUpdate, we must use the price of the list were this quote resides
                var price = Math.Round(this.quotePrice[marketQuoteUpdate.QuoteKey], Decimals);

                // Here is the list where the "add me" or "update me" or "remove me" marketQuoteUpdate reside or will reside
                var quoteList = this[price];

                // Price change => mean that we didn't receive a "close me" marketQuoteUpdate. Generate one before going further !
                if (!marketQuoteUpdate.Closed && price != quoteDotPrice)
                {
                    // Simulate the reception of the needed quote
                    var mods = this.HandleQuote(MarketQuote.ClosingQuote(marketQuoteUpdate.QuoteKey, Timings.Now, marketQuoteUpdate.Side), depth);

                    // Now that the previous quote with same key was closed, the arriving one can be handled
                    var mods2 = this.HandleQuote(marketQuoteUpdate, depth);

                    // The modifications
                    return Modifications.Merge(mods, mods2);
                }

                if (!quoteList.Contains(marketQuoteUpdate.QuoteKey))
                {
                    // Quote is not known

                    // Add this marketQuoteUpdate (this is an "add me" -or- and "update me" for which we weren't there to receive the initial "add me")
                    if (!marketQuoteUpdate.Closed)
                    {
                        quoteList.Add(marketQuoteUpdate);
                    }
                }
                else
                {
                    // Quote is known

                    // Remove this marketQuoteUpdate because closed (it's a "remove me" marketQuoteUpdate)
                    if (marketQuoteUpdate.Closed)
                    {
                        quoteList.Remove(marketQuoteUpdate.QuoteKey);
                        this.quotePrice.Remove(marketQuoteUpdate.QuoteKey);

                        // If there are no more quotes for this particular price, remove list (book will be 1 row shorter on this side)
                        if (quoteList.Count == 0)
                        {
                            this.Remove(price);
                        }
                    }
                    else
                    {
                        // Not closed, it's an "update me" marketQuoteUpdate
                        quoteList.Update(marketQuoteUpdate);
                        indexUpdated = pricesBefore.IndexOf(price);
                    }
                }

                // keep track of prices array after marketQuoteUpdate handled
                pricesAfter = new List<double>(this.Keys);
                {
                    // Generate the modification record
                    var debug = false;
                    if (debug)
                    {
                        Stdout.WriteLine(marketQuoteUpdate);
                        Stdout.WriteLine("Before");
                        foreach (var p in pricesBefore)
                        {
                            Stdout.Write(" " + ForgePrettyPrinter.Prices.Format(p, 0.000001));
                        }

                        Stdout.WriteLine("\nAfter");
                        foreach (var p in pricesAfter)
                        {
                            Stdout.Write(" " + ForgePrettyPrinter.Prices.Format(p, 0.000001));
                        }

                        Stdout.WriteLine("\nModified");
                    }

                    var card = Math.Min(depth, Math.Max(pricesAfter.Count, pricesBefore.Count));
                    IList<short> modified = new List<short>();
                    for (var i = (short)0; i < card; i++)
                    {
                        if (i >= pricesAfter.Count || i >= pricesBefore.Count || pricesBefore[i] != pricesAfter[i] || i == indexUpdated)
                        {
                            modified.Add(i);
                            if (debug)
                            {
                                if (i != indexUpdated)
                                {
                                    Stdout.Write("  " + i);
                                }
                                else
                                {
                                    Stdout.Write(" *" + i);
                                }
                            }
                        }
                    }

                    // modified.Add(indexUpdated);
                    var m = new Modifications();
                    m.ModifiedDepthIndexes = modified.ToArray();
                    m.PreviousLength = pricesBefore.Count;
                    m.NewLength = pricesAfter.Count;
                    return m;
                }
            }

            #endregion

            /// <summary>
            /// The buyers list.
            /// </summary>
            internal class Buyers : MarketQuotesListDictionary
            {
                #region Constructors and Destructors

                /// <summary>
                /// Initializes a new instance of the <see cref="Buyers"/> class.
                /// </summary>
                public Buyers()
                    : base(new BuyersComparer())
                {
                }

                #endregion

                /// <summary>
                /// The comparer for buyers.
                /// </summary>
                public class BuyersComparer : IComparer<double>
                {
                    #region Implemented Interfaces

                    #region IComparer<double>

                    /// <summary>
                    /// The compare.
                    /// </summary>
                    /// <param name="y">
                    /// The y.
                    /// </param>
                    /// <param name="x">
                    /// The x.
                    /// </param>
                    /// <returns>
                    /// The compare.
                    /// </returns>
                    public int Compare(double y, double x)
                    {
                        var t = (int)((x * Constants.InvZero) - (y * Constants.InvZero));
                        return t;
                    }

                    #endregion

                    #endregion
                }
            }

            /// <summary>
            /// The modifications.
            /// </summary>
            internal class Modifications
            {
                #region Properties

                /// <summary>
                /// Gets or sets ModifiedDepthIndexes.
                /// </summary>
                public short[] ModifiedDepthIndexes { get; set; }

                /// <summary>
                /// Gets or sets NewLength.
                /// </summary>
                public int NewLength { get; set; }

                /// <summary>
                /// Gets or sets PreviousLength.
                /// </summary>
                public int PreviousLength { get; set; }

                #endregion

                #region Public Methods

                /// <summary>
                /// The merge.
                /// </summary>
                /// <param name="mod1">
                /// The mod 1.
                /// </param>
                /// <param name="mod2">
                /// The mod 2.
                /// </param>
                /// <returns>
                /// </returns>
                public static Modifications Merge(Modifications mod1, Modifications mod2)
                {
                    if (mod2.ModifiedDepthIndexes.Length == 0)
                    {
                        return mod1;
                    }

                    if (mod1.ModifiedDepthIndexes.Length == 0)
                    {
                        return mod2;
                    }

                    return null;
                }

                #endregion
            }

            /// <summary>
            /// The sellers list.
            /// </summary>
            internal class Sellers : MarketQuotesListDictionary
            {
                #region Constructors and Destructors

                /// <summary>
                /// Initializes a new instance of the <see cref="Sellers"/> class.
                /// </summary>
                public Sellers()
                    : base(new SellersComparer())
                {
                }

                #endregion

                /// <summary>
                /// The comparer for sellers.
                /// </summary>
                public class SellersComparer : IComparer<double>
                {
                    #region Implemented Interfaces

                    #region IComparer<double>

                    /// <summary>
                    /// The compare.
                    /// </summary>
                    /// <param name="x">
                    /// The x.
                    /// </param>
                    /// <param name="y">
                    /// The y.
                    /// </param>
                    /// <returns>
                    /// The compare.
                    /// </returns>
                    public int Compare(double x, double y)
                    {
                        var t = (int)((x * Constants.InvZero) - (y * Constants.InvZero));
                        return t;
                    }

                    #endregion

                    #endregion
                }
            }
        }
    }
}